Skip to content

refactor(core): separate Caplet READMEs from runtime config#219

Merged
ian-pascoe merged 11 commits into
mainfrom
refactor/caplet-operator-readme
Jul 14, 2026
Merged

refactor(core): separate Caplet READMEs from runtime config#219
ian-pascoe merged 11 commits into
mainfrom
refactor/caplet-operator-readme

Conversation

@ian-pascoe

@ian-pascoe ian-pascoe commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Caplet authors can now use the Markdown body as a real operator README without accidentally changing agent behavior, runtime identity, setup approval, or backend reloads. YAML frontmatter is the sole runtime authority; explicitly referenced bundle assets remain runtime inputs, while README prose has an independent content lifecycle.

Design decisions

Boundary Result
Runtime projection Normalized runtime config, backend dispatch, setup, registry/exposure, and Code Mode declarations no longer receive Markdown body content.
Agent context description remains the concise Caplet-level capability description. The redundant useWhen and avoidWhen fields are removed from Caplet and configured-action schemas, projections, generated references, catalog metadata, and the official corpus; downstream tools may still expose backend-owned selection hints.
Runtime identity Versioned semantic fingerprints cover normalized frontmatter, declared referenced inputs, provenance, and host settings without persisting source paths or literal secret values. Missing, unreadable, malformed, or secret-bearing inputs fail closed for reusable setup and lockfile identity.
Reload lifecycle README-only edits are successful no-ops. Runtime-equivalent reloads retain registries and backend clients; runtime or referenced-input changes still fan out normally.
Install/update lifecycle README-only upstream changes report content_updated without setup churn. Artifact and lockfile commits are serialized per lockfile and recover from persisted transaction phases plus observed artifact/lock state.
Human surfaces Catalog and dashboard rendering continue to preserve and display raw Markdown documentation. Authoring guidance and the official Caplet corpus now teach prerequisites, safe operation, troubleshooting, and references instead of agent workflows.

This also establishes a storage-independent contract for a future SQL-backed configuration store: structured runtime fields and operator documentation are separate projections even when they share one portable CAPLET.md artifact today.

Validation

  • pnpm verify
    • formatting, lint, generated Code Mode API, config schema, and docs checks passed
    • typechecking passed across all workspace packages
    • 166 test files passed; 2,348 tests passed
    • deterministic benchmark freshness passed
    • all 8 scheduled build tasks and package runtime validation passed
  • Focused selection-removal contract suite: 8 core files, 309 tests passed; catalog suite, 9 files and 57 tests passed.
  • Transaction coverage exercises every persisted phase, synchronous lock replacement failure, post-commit cleanup failure, overlapping writers, dead-owner recovery, and live-owner preservation.

Compound Engineering
Codex

Summary by CodeRabbit

  • New Features
    • Markdown README-only changes now update catalog content while keeping runtime tools unchanged and skipping setup approval.
    • Install/update flows now report status distinctions including content updated, restored, already current, and runtime updates.
    • Dashboard now shows status-aware catalog-mutation success messaging (including no-op vs content updates).
  • Bug Fixes
    • Installation/update operations are safer with lock/journal recovery, concurrent-update protection, and path traversal/symlink escape prevention.
    • Catalog indexing failures no longer interrupt completed updates.
  • Documentation
    • Clarified that YAML frontmatter drives runtime behavior; Markdown is operator-facing only (and no longer interpolated into runtime).
    • Removed deprecated useWhen/avoidWhen configuration fields.

Fail persistence closed for invalid or secret-bearing runtime inputs, preserve custom source fingerprints, and keep structured selection hints visible without interpolation. Serialize artifact-lock updates and reconcile interrupted transactions from persisted phases plus observed state.
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@ian-pascoe, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d0c55aaa-fa0c-4b8e-b0ee-cb7246c4d8a8

📥 Commits

Reviewing files that changed from the base of the PR and between 740e00c and 58c9ddd.

📒 Files selected for processing (2)
  • packages/core/src/cli/install.ts
  • packages/core/test/cli.test.ts
📝 Walkthrough

Walkthrough

Caplet Markdown bodies are now operator-facing README content rather than runtime configuration. Runtime fingerprints track declared inputs and semantic configuration, enabling no-op reloads, content-only update statuses, transactional lockfile updates, setup approval gating, and body-free agent-facing projections.

Changes

Caplet runtime and README separation

Layer / File(s) Summary
Runtime contract and projections
packages/core/src/config*.ts, packages/core/src/caplet-files-bundle.ts, packages/core/src/*projection*, schemas/*
Runtime schemas and downstream projections remove Markdown body and general useWhen/avoidWhen propagation.
Declared inputs and fingerprints
packages/core/src/caplet-source/*, packages/core/src/config.ts
Declared-input readers, deterministic semantic fingerprints, nested Caplet-set traversal, persistence eligibility, and path safety are implemented.
Transactional lifecycle updates
packages/core/src/cli/install.ts, packages/core/src/cli/lockfile.ts, packages/core/src/cli/setup-caplet.ts
Install, restore, and update flows stage candidates, persist eligible runtime fingerprints, classify content_updated, and recover interrupted transactions.
Reload and presentation integration
packages/core/src/engine.ts, packages/core/src/cloud/*, packages/core/src/current-host/*, apps/dashboard/src/components/*
Runtime-equivalent reloads short-circuit, setup approval persistence is gated, indexing failures are reported as unavailable, and dashboard labels use mutation status.
Documentation and verification
CONCEPTS.md, apps/docs/..., caplets/*, apps/catalog/..., packages/core/test/*
README guidance, catalog content, generated schemas, and tests reflect the separated runtime and operator contracts.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Poem

A rabbit hops through frontmatter bright,
While README pages stay out of runtime sight.
Fingerprints guide each reload and tune,
Transactions keep lockfiles safe as moon.
“Content updated!” the dashboard sings—
And no-op carrots sprout on stable things.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: separating Caplet README content from runtime configuration in core.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/caplet-operator-readme

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/cli.ts (1)

3058-3067: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Install command doesn't label the new content_updated status.

The status union was widened to include "content_updated" (Line 3048), and the update command now uses updateStatusLabel() to surface it distinctly (Lines 4714-4718). But the install command's remote branch (Lines 3059-3064) and local restore branch (Line 3090) still use the old two-way ternaries (noop/restored vs "Installed"), so a content_updated result from restoreCapletsFromLockfile/remote install falls through to the generic "Installed"/"Restored" label instead of "Content updated", undermining the PR's stated goal of surfacing content_updated during install.

🐛 Proposed fix reusing `updateStatusLabel`-style logic
           for (const caplet of result.installed) {
-            const action =
-              caplet.status === "noop"
-                ? "Already installed"
-                : caplet.status === "restored"
-                  ? "Restored"
-                  : "Installed";
+            const action =
+              caplet.status === "noop"
+                ? "Already installed"
+                : caplet.status === "content_updated"
+                  ? "Content updated"
+                  : caplet.status === "restored"
+                    ? "Restored"
+                    : "Installed";
             writeOut(`${action} ${caplet.id} to remote ${caplet.destination}\n`);
             writeCatalogIndexingNotice(caplet.catalogIndexing, writeOut);
           }
           for (const caplet of result.installed) {
             writeOut(
-              `${caplet.status === "noop" ? "Already installed" : "Restored"} ${caplet.id} to ${localMutationTargetLabel(target, io)}${caplet.destination}\n`,
+              `${caplet.status === "noop" ? "Already installed" : caplet.status === "content_updated" ? "Content updated" : "Restored"} ${caplet.id} to ${localMutationTargetLabel(target, io)}${caplet.destination}\n`,
             );

Also applies to: 3088-3094

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/cli.ts` around lines 3058 - 3067, Update the install
command status-label logic in the remote loop and local restore branch to
explicitly handle the “content_updated” status and display “Content updated”.
Reuse the existing updateStatusLabel-style helper or equivalent established
labeling logic while preserving the current “Already installed”, “Restored”, and
“Installed” labels for other statuses.
🧹 Nitpick comments (1)
packages/core/src/cloud/runtime-adapter.ts (1)

84-100: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid recomputing the runtime fingerprint in runSetup.

runtimeFingerprintForConfig(this.engine.currentConfig()) is called in setupPlan (line 84) and again in runSetup (line 120) for the same caplet. Since runSetup already calls setupPlan at line 118, the fingerprint is computed twice. Consider adding persistenceEligible to the SetupPlan return type so runSetup can read it from plan instead of recomputing.

♻️ Proposed refactor
 // In setupPlan return object (around line 101):
 return {
   projectFingerprint,
   capletId,
   name: caplet.name,
   contentHash,
   targetKind,
   setup: caplet.setup ?? {},
   approved,
+  persistenceEligible: runtimeFingerprint?.persistenceEligible ?? true,
   commands: caplet.setup?.commands ?? [],
   verify: caplet.setup?.verify ?? [],
 };

 // In runSetup (around line 119):
-const persistenceEligible =
-  runtimeFingerprintForConfig(this.engine.currentConfig())?.caplets[capletId]
-    ?.persistenceEligible ?? true;
+const persistenceEligible = plan.persistenceEligible;

Also applies to: 119-122

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/cloud/runtime-adapter.ts` around lines 84 - 100, Update the
SetupPlan return value and setupPlan flow to include the caplet’s
persistenceEligible status from the existing runtime fingerprint. In runSetup,
reuse that value from the plan returned by setupPlan when determining approval,
and remove the second runtimeFingerprintForConfig call while preserving the
current approval behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/core/src/cli.ts`:
- Around line 3058-3067: Update the install command status-label logic in the
remote loop and local restore branch to explicitly handle the “content_updated”
status and display “Content updated”. Reuse the existing updateStatusLabel-style
helper or equivalent established labeling logic while preserving the current
“Already installed”, “Restored”, and “Installed” labels for other statuses.

---

Nitpick comments:
In `@packages/core/src/cloud/runtime-adapter.ts`:
- Around line 84-100: Update the SetupPlan return value and setupPlan flow to
include the caplet’s persistenceEligible status from the existing runtime
fingerprint. In runSetup, reuse that value from the plan returned by setupPlan
when determining approval, and remove the second runtimeFingerprintForConfig
call while preserving the current approval behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5a469b01-6f38-4f53-b3bf-28dfc917f503

📥 Commits

Reviewing files that changed from the base of the PR and between 98df606 and bb2774f.

📒 Files selected for processing (82)
  • .changeset/calm-caplet-readme-contract.md
  • CONCEPTS.md
  • apps/catalog/src/data/official-catalog.json
  • apps/dashboard/src/components/DashboardApp.test.tsx
  • apps/dashboard/src/components/DashboardApp.tsx
  • apps/docs/src/content/docs/reference/caplet-files.mdx
  • apps/docs/src/content/docs/vault.mdx
  • caplets/ast-grep/CAPLET.md
  • caplets/aws/CAPLET.md
  • caplets/azure/CAPLET.md
  • caplets/browser-use/CAPLET.md
  • caplets/cloudflare/CAPLET.md
  • caplets/coding-agent-toolkit/CAPLET.md
  • caplets/computer-use/CAPLET.md
  • caplets/context7/CAPLET.md
  • caplets/datadog/CAPLET.md
  • caplets/deepwiki/CAPLET.md
  • caplets/github/CAPLET.md
  • caplets/gmail/CAPLET.md
  • caplets/google-chat/CAPLET.md
  • caplets/google-docs/CAPLET.md
  • caplets/google-drive/CAPLET.md
  • caplets/google-forms/CAPLET.md
  • caplets/google-meet/CAPLET.md
  • caplets/google-sheets/CAPLET.md
  • caplets/google-slides/CAPLET.md
  • caplets/google-tasks/CAPLET.md
  • caplets/google-workspace/CAPLET.md
  • caplets/linear/CAPLET.md
  • caplets/lsp/CAPLET.md
  • caplets/mongodb/CAPLET.md
  • caplets/neon/CAPLET.md
  • caplets/notion/CAPLET.md
  • caplets/npm/CAPLET.md
  • caplets/osv/CAPLET.md
  • caplets/pagerduty/CAPLET.md
  • caplets/playwright/CAPLET.md
  • caplets/posthog/CAPLET.md
  • caplets/pypi/CAPLET.md
  • caplets/repo-cli/CAPLET.md
  • caplets/sentry/CAPLET.md
  • caplets/sourcegraph/CAPLET.md
  • caplets/stripe/CAPLET.md
  • caplets/supabase/CAPLET.md
  • caplets/terraform/CAPLET.md
  • caplets/vercel/CAPLET.md
  • docs/plans/2026-07-13-001-refactor-caplet-operator-readme-separation-plan.md
  • packages/core/src/caplet-files-bundle.ts
  • packages/core/src/caplet-source/bundle.ts
  • packages/core/src/caplet-source/filesystem.ts
  • packages/core/src/caplet-source/index.ts
  • packages/core/src/caplet-source/parse.ts
  • packages/core/src/caplet-source/runtime-fingerprint.ts
  • packages/core/src/caplet-source/types.ts
  • packages/core/src/cli.ts
  • packages/core/src/cli/author.ts
  • packages/core/src/cli/install.ts
  • packages/core/src/cli/lockfile.ts
  • packages/core/src/cli/setup-caplet.ts
  • packages/core/src/cloud/runtime-adapter.ts
  • packages/core/src/code-mode/declarations.ts
  • packages/core/src/config-runtime.ts
  • packages/core/src/config.ts
  • packages/core/src/current-host/catalog-operations.ts
  • packages/core/src/current-host/catalog.ts
  • packages/core/src/engine.ts
  • packages/core/src/index.ts
  • packages/core/src/setup/hash.ts
  • packages/core/test/backend-operation-dispatch.test.ts
  • packages/core/test/caplet-files.test.ts
  • packages/core/test/caplet-source.test.ts
  • packages/core/test/caplets-lockfile.test.ts
  • packages/core/test/cli.test.ts
  • packages/core/test/code-mode-declarations.test.ts
  • packages/core/test/config.test.ts
  • packages/core/test/current-host-catalog-operations.test.ts
  • packages/core/test/engine.test.ts
  • packages/core/test/native.test.ts
  • packages/core/test/runtime-fingerprint.test.ts
  • packages/core/test/serve-http.test.ts
  • packages/core/test/setup-runner.test.ts
  • scripts/generate-docs-reference.ts

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR separates Caplet README prose from runtime configuration: YAML frontmatter is now the sole runtime authority, while the Markdown body is treated as operator documentation with an independent content lifecycle. It removes useWhen/avoidWhen/body from all Caplet config types and schemas, introduces a domain-separated SHA-256 fingerprinting system (runtime-fingerprint.ts) covering normalized frontmatter and declared inputs, and adds a staged two-phase commit transaction protocol with journal-based crash recovery, advisory lock with dead-PID recovery, and overlapping-writer protection for the restore/update install paths.

  • README separation: Markdown body no longer flows into runtime projections, agent context, backend dispatch, or setup hashes; only YAML frontmatter drives runtime identity and fingerprinting.
  • Transaction machinery: Restore and update now stage artifacts before committing, record a per-caplet journal through each phase, and recover interrupted transactions on next invocation; catalog indexing failures are now isolated and no longer block a completed install.
  • No-op reload detection: The engine short-circuits config reloads when both the hostConfigurationFingerprint and resolvedExecutionFingerprint are unchanged, preserving registries and backend clients for README-only edits.

Confidence Score: 5/5

Safe to merge. The transaction machinery, crash recovery, fingerprinting, and reload no-op detection are all sound; the one gap in installCapletsUnlocked is a narrow edge case that requires an unusual sequence of events and produces a clear error rather than silent data corruption.

The core changes — README/frontmatter separation, domain-separated fingerprinting, two-phase commit with journal recovery, and advisory lock with dead-PID recovery — are well-structured and backed by a comprehensive test suite (2,348 tests, dedicated transaction phase coverage). The only structural gap is that installCapletsUnlocked does not call recoverInterruptedCapletTransactions, meaning a journal orphaned by an interrupted restore/update for the same caplet ID could block future restore/update if install runs in between. This requires an unusual workflow sequence and fails loudly rather than corrupting state, so it does not affect overall merge safety.

packages/core/src/cli/install.ts — specifically the installCapletsUnlocked function's missing journal recovery step when a lockfile path is present.

Important Files Changed

Filename Overview
packages/core/src/cli/install.ts Major refactor introducing staged-transaction installs with journal-based crash recovery, advisory lock with dead-PID recovery, and a new content_updated status for README-only upstream changes. One gap: installCapletsUnlocked doesn't run recoverInterruptedCapletTransactions, so a journal left by an interrupted restore/update for the same caplet ID could be orphaned by a subsequent install and block future restore/update.
packages/core/src/caplet-source/runtime-fingerprint.ts New module producing domain-separated SHA-256 fingerprints over normalized frontmatter, declared inputs, provenance, and host settings. Path normalization, cycle detection, and secret-path exclusions are all handled.
packages/core/src/cli/lockfile.ts Adds CapletsLockRuntimeFingerprint field to lock entries and splits writeCapletsLockfile into writeCapletsLockfileTemporary + replaceCapletsLockfileTemporary to support the two-phase commit protocol. runtimeFingerprint is persisted but currently unused for the content_updated comparison.
packages/core/src/caplet-source/parse.ts Integrates runtime fingerprinting into parse: declared input existence is now determined from the fingerprint snapshot rather than direct file reads, and runtimeFingerprint is included in the parse result for downstream consumers.
packages/core/src/engine.ts Adds fingerprint-based reload no-op detection: if both hostConfigurationFingerprint and resolvedExecutionFingerprint are unchanged, the reload returns early without rebuilding registries or backend clients.
packages/core/src/config.ts Removes useWhen/avoidWhen fields and the body field from all Caplet config types and Zod schemas. Adds runtimeFingerprintForConfig with WeakMap caching.
packages/core/src/current-host/catalog-operations.ts Wraps indexInstalledCapletsFromLockfile in a try/catch so catalog indexing failures no longer propagate and block a completed install/update.
apps/dashboard/src/components/DashboardApp.tsx Adds status-aware UI messaging for the new content_updated and restored statuses. Clean extension of the existing message pattern.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant U as User
    participant IT as installCaplets / restoreCaplets / updateCaplets
    participant TL as withLockfileTransaction
    participant RT as recoverInterruptedTxns
    participant SA as stageAndValidateCandidate
    participant CS as commitStagedCapletAndLock
    participant LF as Lockfile

    U->>IT: restore / update (lockfilePath)
    IT->>TL: acquire .transaction.lock (O_EXCL)
    TL->>RT: recoverInterruptedCapletTransactions
    RT->>LF: "read journals (.caplet-txn-*.json)"
    RT->>LF: advance / revert lock as needed
    TL->>SA: copyInstallPath to staged artifact
    SA->>SA: fingerprintArtifact (runtime snapshot)
    SA->>SA: hashInstalledArtifact
    SA-->>TL: CandidateArtifact
    TL->>CS: write journal phases (prepared through lock_replaced)
    CS->>LF: writeLockfileTemporary + replaceLockfileTemporary
    CS->>CS: cleanupTransactionPaths (remove journal)
    TL->>TL: release .transaction.lock
    IT-->>U: installed[] with status (noop / restored / content_updated / updated)

    Note over U,IT: installCaplets (fresh install) skips recoverInterruptedTxns — stale journals for the same caplet ID are not cleaned up
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant U as User
    participant IT as installCaplets / restoreCaplets / updateCaplets
    participant TL as withLockfileTransaction
    participant RT as recoverInterruptedTxns
    participant SA as stageAndValidateCandidate
    participant CS as commitStagedCapletAndLock
    participant LF as Lockfile

    U->>IT: restore / update (lockfilePath)
    IT->>TL: acquire .transaction.lock (O_EXCL)
    TL->>RT: recoverInterruptedCapletTransactions
    RT->>LF: "read journals (.caplet-txn-*.json)"
    RT->>LF: advance / revert lock as needed
    TL->>SA: copyInstallPath to staged artifact
    SA->>SA: fingerprintArtifact (runtime snapshot)
    SA->>SA: hashInstalledArtifact
    SA-->>TL: CandidateArtifact
    TL->>CS: write journal phases (prepared through lock_replaced)
    CS->>LF: writeLockfileTemporary + replaceLockfileTemporary
    CS->>CS: cleanupTransactionPaths (remove journal)
    TL->>TL: release .transaction.lock
    IT-->>U: installed[] with status (noop / restored / content_updated / updated)

    Note over U,IT: installCaplets (fresh install) skips recoverInterruptedTxns — stale journals for the same caplet ID are not cleaned up
Loading

Reviews (4): Last reviewed commit: "fix(core): preserve lock identity during..." | Re-trigger Greptile

Comment thread packages/core/src/cli/install.ts
Comment thread packages/core/src/cli/install.ts
Comment thread packages/core/src/cli/install.ts
Comment thread packages/core/src/config.ts
Recover dead-owner transaction locks without weakening live-writer exclusion, keep staging and commit on one transaction path set, and expose content-only install statuses consistently. Document literal selection-hint migration and reuse hosted setup fingerprint eligibility.
@ian-pascoe

Copy link
Copy Markdown
Contributor Author

Install command doesn't label the new content_updated status.

Addressed: both remote install and local lockfile-restore human output now render content_updated as Content updated, while preserving Already installed, Restored, and Installed. Focused output tests cover both branches.

Avoid recomputing the runtime fingerprint in runSetup.

Addressed: SetupPlan now carries the persistence-eligibility result derived by setupPlan, and runSetup reuses that exact value. The existing false behavior for ineligible fingerprints and true default for missing fingerprints remain covered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/core/src/cli/install.ts (1)

448-560: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Significant duplication between restoreCapletsFromLockfileUnlocked and updateCapletsFromLockfileUnlocked.

Both functions (this range and Lines 343-428) repeat the same stage → prospective-entry → lock-changed detection → noop/commit branching → result-push → finally-cleanup skeleton, differing only in status derivation (restored vs noop/content_updated/updated) and a couple of update-specific checks (locked-source availability, risk gating). This duplication risks the two paths silently diverging on future fixes (e.g., a bug fix to the staged-cleanup or candidate-lifecycle handling applied to one but not the other).

Consider extracting the shared staging/commit/result-assembly flow into a helper parameterized by a status-resolution callback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/cli/install.ts` around lines 448 - 560, Extract the shared
stage, prospective-entry, lock-change, status branching, commit, result
assembly, and staged-candidate cleanup flow from
restoreCapletsFromLockfileUnlocked and updateCapletsFromLockfileUnlocked into a
helper. Parameterize it with the status-resolution callback and preserve each
caller’s unique source validation, risk gating, and restored versus update
status behavior; then have both functions use the helper to keep candidate
lifecycle handling consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/core/src/cli/install.ts`:
- Around line 448-560: Extract the shared stage, prospective-entry, lock-change,
status branching, commit, result assembly, and staged-candidate cleanup flow
from restoreCapletsFromLockfileUnlocked and updateCapletsFromLockfileUnlocked
into a helper. Parameterize it with the status-resolution callback and preserve
each caller’s unique source validation, risk gating, and restored versus update
status behavior; then have both functions use the helper to keep candidate
lifecycle handling consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fde6b67b-154f-4284-8b29-16f56fd030c7

📥 Commits

Reviewing files that changed from the base of the PR and between bb2774f and d1852a6.

⛔ Files ignored due to path filters (1)
  • packages/core/src/code-mode/runtime-api.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (84)
  • .changeset/calm-caplet-readme-contract.md
  • apps/catalog/src/data/official-catalog.json
  • apps/catalog/src/lib/ingest.ts
  • apps/catalog/test/ingest.test.ts
  • apps/docs/src/content/docs/reference/caplet-files.mdx
  • apps/docs/src/content/docs/reference/code-mode-api.mdx
  • apps/docs/src/content/docs/reference/config.mdx
  • apps/docs/src/content/docs/vault.mdx
  • apps/landing/public/caplet.schema.json
  • apps/landing/public/config.schema.json
  • caplets/ast-grep/CAPLET.md
  • caplets/aws/CAPLET.md
  • caplets/azure/CAPLET.md
  • caplets/browser-use/CAPLET.md
  • caplets/cloudflare/CAPLET.md
  • caplets/coding-agent-toolkit/CAPLET.md
  • caplets/computer-use/CAPLET.md
  • caplets/context7/CAPLET.md
  • caplets/datadog/CAPLET.md
  • caplets/deepwiki/CAPLET.md
  • caplets/github/CAPLET.md
  • caplets/gmail/CAPLET.md
  • caplets/google-chat/CAPLET.md
  • caplets/google-docs/CAPLET.md
  • caplets/google-drive/CAPLET.md
  • caplets/google-meet/CAPLET.md
  • caplets/google-sheets/CAPLET.md
  • caplets/google-slides/CAPLET.md
  • caplets/google-tasks/CAPLET.md
  • caplets/google-workspace/CAPLET.md
  • caplets/lsp/CAPLET.md
  • caplets/mongodb/CAPLET.md
  • caplets/neon/CAPLET.md
  • caplets/notion/CAPLET.md
  • caplets/npm/CAPLET.md
  • caplets/osv/CAPLET.md
  • caplets/pagerduty/CAPLET.md
  • caplets/playwright/CAPLET.md
  • caplets/pypi/CAPLET.md
  • caplets/sourcegraph/CAPLET.md
  • caplets/stripe/CAPLET.md
  • caplets/supabase/CAPLET.md
  • caplets/terraform/CAPLET.md
  • caplets/vercel/CAPLET.md
  • docs/plans/2026-07-13-001-refactor-caplet-operator-readme-separation-plan.md
  • packages/core/src/attach/api.ts
  • packages/core/src/capability-description.ts
  • packages/core/src/caplet-files-bundle.ts
  • packages/core/src/catalog/entry.ts
  • packages/core/src/catalog/types.ts
  • packages/core/src/cli-tools.ts
  • packages/core/src/cli.ts
  • packages/core/src/cli/code-mode.ts
  • packages/core/src/cli/install.ts
  • packages/core/src/cloud/runtime-adapter.ts
  • packages/core/src/code-mode/declarations.ts
  • packages/core/src/code-mode/runtime-api.d.ts
  • packages/core/src/code-mode/types.ts
  • packages/core/src/config-runtime.ts
  • packages/core/src/config.ts
  • packages/core/src/current-host/catalog.ts
  • packages/core/src/exposure/projection.ts
  • packages/core/src/graphql.ts
  • packages/core/src/http-actions.ts
  • packages/core/src/native/remote.ts
  • packages/core/src/native/service.ts
  • packages/core/src/registry.ts
  • packages/core/src/serve/session.ts
  • packages/core/src/setup/types.ts
  • packages/core/test/attach-api.test.ts
  • packages/core/test/catalog-model.test.ts
  • packages/core/test/cli-install-output.test.ts
  • packages/core/test/cli-remote.test.ts
  • packages/core/test/cli.test.ts
  • packages/core/test/code-mode-declarations.test.ts
  • packages/core/test/config.test.ts
  • packages/core/test/native-remote.test.ts
  • packages/core/test/native.test.ts
  • packages/core/test/registry.test.ts
  • packages/core/test/runtime-fingerprint.test.ts
  • schemas/caplet.schema.json
  • schemas/caplets-config.schema.json
  • scripts/generate-catalog-index.ts
  • scripts/generate-docs-reference.ts
💤 Files with no reviewable changes (61)
  • packages/core/src/capability-description.ts
  • apps/docs/src/content/docs/reference/code-mode-api.mdx
  • packages/core/src/code-mode/types.ts
  • packages/core/src/catalog/entry.ts
  • caplets/browser-use/CAPLET.md
  • caplets/google-meet/CAPLET.md
  • caplets/google-chat/CAPLET.md
  • caplets/coding-agent-toolkit/CAPLET.md
  • caplets/ast-grep/CAPLET.md
  • packages/core/src/cli/code-mode.ts
  • caplets/context7/CAPLET.md
  • caplets/computer-use/CAPLET.md
  • packages/core/src/http-actions.ts
  • caplets/playwright/CAPLET.md
  • caplets/google-docs/CAPLET.md
  • packages/core/src/catalog/types.ts
  • caplets/google-slides/CAPLET.md
  • caplets/azure/CAPLET.md
  • packages/core/src/cli-tools.ts
  • caplets/gmail/CAPLET.md
  • apps/catalog/src/lib/ingest.ts
  • apps/docs/src/content/docs/reference/config.mdx
  • caplets/datadog/CAPLET.md
  • caplets/vercel/CAPLET.md
  • caplets/github/CAPLET.md
  • packages/core/src/serve/session.ts
  • caplets/npm/CAPLET.md
  • caplets/osv/CAPLET.md
  • caplets/lsp/CAPLET.md
  • caplets/terraform/CAPLET.md
  • caplets/google-workspace/CAPLET.md
  • caplets/sourcegraph/CAPLET.md
  • caplets/google-drive/CAPLET.md
  • caplets/supabase/CAPLET.md
  • packages/core/src/registry.ts
  • packages/core/test/native-remote.test.ts
  • caplets/cloudflare/CAPLET.md
  • scripts/generate-catalog-index.ts
  • caplets/pypi/CAPLET.md
  • caplets/stripe/CAPLET.md
  • packages/core/src/code-mode/runtime-api.d.ts
  • caplets/deepwiki/CAPLET.md
  • packages/core/src/graphql.ts
  • packages/core/src/native/service.ts
  • apps/catalog/test/ingest.test.ts
  • packages/core/test/registry.test.ts
  • caplets/notion/CAPLET.md
  • caplets/aws/CAPLET.md
  • caplets/mongodb/CAPLET.md
  • packages/core/test/attach-api.test.ts
  • caplets/pagerduty/CAPLET.md
  • caplets/google-tasks/CAPLET.md
  • caplets/neon/CAPLET.md
  • caplets/google-sheets/CAPLET.md
  • apps/landing/public/config.schema.json
  • schemas/caplets-config.schema.json
  • apps/landing/public/caplet.schema.json
  • schemas/caplet.schema.json
  • packages/core/test/native.test.ts
  • packages/core/src/current-host/catalog.ts
  • packages/core/src/caplet-files-bundle.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • .changeset/calm-caplet-readme-contract.md
  • apps/docs/src/content/docs/vault.mdx
  • packages/core/src/cli.ts
  • packages/core/src/cloud/runtime-adapter.ts
  • docs/plans/2026-07-13-001-refactor-caplet-operator-readme-separation-plan.md
  • packages/core/test/runtime-fingerprint.test.ts

@ian-pascoe

Copy link
Copy Markdown
Contributor Author

fingerprintInstalledArtifact throws without a fallback when the currently installed artifact's CAPLET.md is missing or unreadable, turning a previously recoverable situation into a hard error that even --force cannot bypass.

Addressed: installed-artifact fingerprinting is now best-effort and falls back to updated when the old runtime fingerprint cannot be derived, while staged candidate validation and risk checks remain strict. Added a regression proving --force replaces an installation missing CAPLET.md and reports updated.

@ian-pascoe

Copy link
Copy Markdown
Contributor Author

Significant duplication between restoreCapletsFromLockfileUnlocked and updateCapletsFromLockfileUnlocked risks the two paths silently diverging on future fixes.

Addressed: extracted the shared candidate finalization seam into finalizeStagedCandidate, which owns prospective lock-entry assembly, lock-change detection, atomic commit, result construction, and pre-commit staged cleanup. Restore/update source resolution, status derivation, availability checks, and risk gating remain in their respective flows.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/cli/install.ts`:
- Around line 379-390: Validate the staged candidate’s artifact hash against
entry.installedHash in the restore path before calling finalizeStagedCandidate.
Reject the restore when they differ, preserving the existing locked entry and
preventing prospective from replacing its hash; only the update flow may advance
the lock entry.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: dcf0c53d-3140-4fec-b0df-036a092644f1

📥 Commits

Reviewing files that changed from the base of the PR and between d1852a6 and 740e00c.

📒 Files selected for processing (2)
  • packages/core/src/cli/install.ts
  • packages/core/test/cli.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/test/cli.test.ts

Comment thread packages/core/src/cli/install.ts
@ian-pascoe
ian-pascoe merged commit b165ac0 into main Jul 14, 2026
8 checks passed
@ian-pascoe
ian-pascoe deleted the refactor/caplet-operator-readme branch July 14, 2026 11:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant